import { factory } from "../utils/html";
import mono_hlsb from "../utils/mono_hmsb_in_worker";
import { TileSelector } from "./TileSelector";
import { ComponentClass } from "./ComponentClass";
const canvas = factory('canvas');
const cssPixelated ='image-rendering: optimizeSpeed;' + // FireFox < 6.0
                    'image-rendering: -moz-crisp-edges;' + // FireFox
                    'image-rendering: -o-crisp-edges;' +  // Opera
                    'image-rendering: -webkit-crisp-edges;' + // Chrome
                    'image-rendering: crisp-edges;' + // Chrome
                    'image-rendering: -webkit-optimize-contrast;' + // Safari
                    'image-rendering: pixelated; ' + // Future browsers
                    '-ms-interpolation-mode: nearest-neighbor;'; // IE
export class MapEditor extends ComponentClass{
  /**
   * 
   * @param {TileSelector} tileSelector 
   * @param {number} width 
   * @param {number} height 
   * @param {number} scale 
   * @param {(gridX:number, gridY:number, mapEditor:MapEditor, event:MouseEvent) => void} onGridClick
   * @param {(gridX:number, gridY:number, mapEditor:MapEditor, event:MouseEvent) => void} onGridHover
   */
  constructor(
    tileSelector,
    width=16,
    height=16,
    scale=2,
    onGridClick=undefined,
    onGridHover=undefined,
  ){
    super();
    let cv = canvas({
      width: width * tileSelector.blockWidth * scale,
      height: height * tileSelector.blockHeight * scale,
      style: {cssText:cssPixelated}
    });
    /** @type {HTMLCanvasElement} */
    this.element = cv;
    this.tileSelector = tileSelector;
    this.width = width,
    this.height = height,
    this.scale = scale;
    this.onGridClick = onGridClick;
    this.onGridHover = onGridHover;
    this.mapTileList = new Uint8Array(width * height); // each grid use one tile, save the tile index.
    this.lastHoverGrid = {x:-1, y:-1};
    // render
    this.isRenderBusy = false;
    this.hasRenderRequest = false;
    cv.onmousedown = this.__handleOnClick.bind(this);
    cv.onmousemove = this.__handleOnMove.bind(this);
    cv.onmouseleave = this.__handleOnLeave.bind(this);
    cv.addEventListener("contextmenu",event=>event.preventDefault())
    // this.renderPromise = this.render();
  }
  valueOf(){
    return this.element;
  }

  // render function below
  async render(targetTileIndex=-1){
    if (this.isRenderBusy) {
      this.hasRenderRequest = true;
      return;
    }
    this.isRenderBusy = true;
    // create tmp canvas
    let cv = canvas({
      width: this.element.width,
      height: this.element.height,
    })
    /** @type {CanvasRenderingContext2D} */
    let ctx = cv.getContext("2d", { willReadFrequently: true });
    /** @type {CanvasRenderingContext2D} */
    let ctxo = this.element.getContext("2d", { willReadFrequently: true });
    let imgDataAll = ctxo.getImageData(0, 0, this.element.width, this.element.height);
    ctx.putImageData(imgDataAll, 0, 0);
    // off screen render
    for (let x=0;x<this.width;x++){
      for (let y=0;y<this.height;y++){
        if (targetTileIndex < 0 || targetTileIndex == this.getTileIndexAt(x, y)){
          await this.__renderGridItem(ctx, x, y);
        }
      }
    }
    // apply data
    imgDataAll = ctx.getImageData(0, 0, this.element.width, this.element.height)
    ctxo.putImageData(imgDataAll, 0, 0);
    //
    this.isRenderBusy = false;
    if (this.hasRenderRequest){
      this.renderPromise = this.render();
    }
  }
  __getMapIndex(x, y){
    return this.width * y + x;
  }
  __getGridRect(x, y){
    let cx = x * this.scale * this.tileSelector.blockWidth;
    let cy = y * this.scale * this.tileSelector.blockHeight;
    let w = this.scale * this.tileSelector.blockWidth;
    let h = this.scale * this.tileSelector.blockHeight;
    return { x:cx, y:cy, w, h };
  }
  async __renderGridItem(ctx, x, y, tileIndex=-1, highlight=false){
    if (tileIndex < 0){
      tileIndex = this.getTileIndexAt(x, y);
    }
    let monoData = this.tileSelector.getTileBytes(tileIndex);
    if (monoData.length != this.tileSelector.blockDataSize){
      // index is not correct, try zero
      monoData = this.tileSelector.getTileBytes(0);
      if (monoData.length != this.tileSelector.blockDataSize) return; // still not correct
      this.setTileIndexAt(x, y, 0, false);
    }
    let rect = this.__getGridRect(x, y);
    /** @type {ImageData} */
    let canvasData = ctx.createImageData(rect.w, rect.h);
    if (highlight){
      canvasData.data.set(await mono_hlsb.drawCanvasDataInWorker(monoData, canvasData.width, canvasData.height, this.scale, this.color, this.tileSelector.highlightColor));
    }else{
      canvasData.data.set(await mono_hlsb.drawCanvasDataInWorker(monoData, canvasData.width, canvasData.height, this.scale, this.tileSelector.color));
    }
    ctx.putImageData(canvasData, rect.x, rect.y);
  }

  // event handler below
  __getMousePositionFromEvent(event){
    // convert to canvas axis
    let x = this.element.width*event.offsetX/this.element.clientWidth;
    let y = this.element.height*event.offsetY/this.element.clientHeight;
    let gridWidth = this.scale * this.tileSelector.blockWidth;
    let gridHeight = this.scale * this.tileSelector.blockHeight;
    let col = Math.floor(x / gridWidth);
    let row = Math.floor(y / gridHeight);
    return {x: col, y: row};
  }
  __handleOnClick(event){
    let {x, y} = this.__getMousePositionFromEvent(event);
    if (x < 0 || y < 0 || x >= this.width || y >= this.height){
      return;
    }
    if (typeof(this.onGridClick) == "function"){
      this.onGridClick(x, y, this, event);
    }
  }
  __handleOnMove(event){
    let {x, y} = this.__getMousePositionFromEvent(event);
    if (x < 0 || y < 0 || x >= this.width || y >= this.height){
      return;
    }
    if (x == this.lastHoverGrid.x && y == this.lastHoverGrid.y){
      return; // not trigger when in same grid
    }
    if (typeof(this.onGridHover) == "function"){
      this.onGridHover(x, y, this, event);
    }
  }
  __handleOnLeave(event){
    if (typeof(this.onGridHover) == "function"){
      this.onGridHover(-1, -1, this, event);
    }
  }

  // public function
  getTileIndexAt(x, y){
    if (x < 0 || y < 0 || x >= this.width || y >= this.height){
      return -1;
    }
    return this.mapTileList[this.__getMapIndex(x, y)];
  }
  setTileIndexAt(x, y, index, refresh=true){
    if (x < 0 || y < 0 || x >= this.width || y >= this.height){
      return;
    }
    this.mapTileList[this.__getMapIndex(x, y)] = index;
    if (refresh && this.lastHoverGrid.x != x && this.lastHoverGrid.y != y){
      let ctx = this.element.getContext("2d", { willReadFrequently: true });
      this.__renderGridItem(ctx, x, y);
    }
    // this.hoverTileIndexAt(-1, -1, -1); // cancel highlight
  }
  toggleTileIndexAt(x, y, index, refresh=true){
    if (x < 0 || y < 0 || x >= this.width || y >= this.height){
      return;
    }
    if (index !== this.getTileIndexAt(x, y)){
      this.setTileIndexAt(x, y, index, refresh);
    }else{
      this.setTileIndexAt(x, y, 0, refresh);
    }
  }
  hoverTileIndexAt(x, y, index){
    let ctx = this.element.getContext("2d", { willReadFrequently: true });
    if (this.lastHoverGrid.x >= 0 && this.lastHoverGrid.y >= 0){
      this.__renderGridItem(ctx, this.lastHoverGrid.x, this.lastHoverGrid.y);
    }
    if (x < 0 || y < 0 || x >= this.width || y >= this.height){
      this.lastHoverGrid = {x: -1, y: -1};
      return;
    }else{
      this.lastHoverGrid = {x, y};
      this.__renderGridItem(ctx, x, y, index, true);
    }
  }
  setTileSelector(newSelector, refresh=true){
    this.tileSelector = newSelector;
    this.element.width = this.width * this.tileSelector.blockWidth * this.scale;
    this.element.height = this.height * this.tileSelector.blockHeight * this.scale;
    if (refresh){
      this.renderPromise = this.render();
    }
  }
} 